#pragma warning(disable : 4786)

#include "particleSystem.h"

#include "ForceGravity.h"
#include "ForceDrag.h"
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <cmath>

//#include <limits>

using namespace std;

static float prevT;

/***************
 * Constructors
 ***************/

ParticleSystem::ParticleSystem() 
{	
	n = 0;
	nPerFrame = 4;
	forces = new Force*[FORCE_TYPE_COUNT];
	forces[FORCE_TYPE_GRAVITY] = new ForceGravity();
	forces[FORCE_TYPE_DRAG] = new ForceDrag();
	cm = CollisionManager(&particlesList);
	
	//	add some walls for collision 
	Vec3f p1(-5, 2, -2);
	Vec3f p2(-5, 5, -2);
	Vec3f p3(-5, 5, 2);
	Vec3f p4(-5, 2, 2);
	cm.addWall(p1, p2, p3, p4);
	
	p1 = Vec3f(-3, -6, -2);
	p2 = Vec3f(1, -2, -2);
	p3 = Vec3f(1, -2, 2);
	p4 = Vec3f(-3, -6, 2);
	cm.addWall(p1, p2, p3, p4);
	
	p1 = Vec3f(-7, -6, -2);
	p2 = Vec3f(-7, -2, -2);
	p3 = Vec3f(-10, -2, 2);
	p4 = Vec3f(-10, -6, 2);
	cm.addWall(p1, p2, p3, p4);
	
}



/*************
 * Destructor
 *************/

ParticleSystem::~ParticleSystem() 
{
	// TODO

}


/******************
 * Simulation fxns
 ******************/

/** Start the simulation */
void ParticleSystem::startSimulation(float t)
{
    // TODO

	// These values are used by the UI ...
	// negative bake_end_time indicates that simulation
	// is still progressing, and allows the
	// indicator window above the time slider
	// to correctly show the "baked" region
	// in grey.
	bake_end_time = -1;
	simulate = true;
	dirty = true;

}

/** Stop the simulation */
void ParticleSystem::stopSimulation(float t)
{
    
	// TODO

	// These values are used by the UI
	simulate = false;
	dirty = true;

}

/** Reset the simulation */
void ParticleSystem::resetSimulation(float t)
{
    
	// TODO

	// These values are used by the UI
	simulate = false;
	dirty = true;

}

/** Compute forces and update particles **/
void ParticleSystem::computeForcesAndUpdateParticles(float t)
{
    if(t == prevT) return;	//no time has passed
    
    int erased = 0;
	// TODO

	if (simulate) {
		//EMIT new particles
		emitWater(nPerFrame);
		
		list<Particle>::iterator p;
		for(p = particlesList.begin(); p != particlesList.end(); ++p) {
			//Check life
			if(p->checkLife()) {
				p = particlesList.erase(p);
				erased++;
				continue;
			}
			//Clear forces
			p->resetForce();

			//ADD FORCES:
			for(int f = 0; f < FORCE_TYPE_COUNT; ++f) {
				forces[f]->applyForce(*p);
			}
		}	
		//SOLVER (euler):
		eulerSolver(t-prevT);
		cm.update(t-prevT);
	}
	
	// Debugging info
	if( t - prevT > .04 )
		printf("(!!) Dropped Frame %lf (!!)\n", t-prevT);
	prevT = t;
	//cout << "Erased: " << erased << endl;
	//fcout << "Particles: " << particlesList.size() << endl;
}


void ParticleSystem::eulerSolver(float dt)
{
	list<Particle>::iterator p;
	for(p = particlesList.begin(); p != particlesList.end(); ++p) {
		p->setPosition( p->getPosition() + (dt * p->getVelocity()) );
		p->setVelocity( p->getVelocity() + (dt * (p->getForce()/p->getMass()) ) );
	}
}

//	add a wall for collision detection.  vertices in order please
void ParticleSystem::addWall(Vec3f p1, Vec3f p2, Vec3f p3, Vec3f p4)
{
	cm.addWall(p1, p2, p3, p4);
}


void ParticleSystem::emitWater(int np)
{
    Vec4f v = 6 * emitterNormal;
    
	for (int i=0; i < np; ++i) {
		Particle p;
		p.setPosition(emitterPos[0], emitterPos[1]-0.5, emitterPos[2]-0.5);
		p.setVelocity(v[0], v[1], v[2]);
		p.setRandomMass(10, 15);
		p.setRandomSize(5, 20);
		p.resetForce();
		p.setAge(0, 100);
		particlesList.push_back(p);
	}
	n += np;
}


/** Render particles */
void ParticleSystem::drawParticles(float t)
{
    //TODO
   
    //DRAWING PARTICLES
    list<Particle>::iterator p;    
    for(p = particlesList.begin(); p != particlesList.end(); ++p) {
        p->draw();
    }	
}

void ParticleSystem::drawEmitter(Vec3f *outNormal) {   
    glBegin(GL_QUADS);
    (*outNormal)[0] = 0.0;
    (*outNormal)[1] = 1.0;
    (*outNormal)[2] = 0.0;
    glNormal3d( (*outNormal)[0], (*outNormal)[1], (*outNormal)[2] );
    glVertex3d( 0.0, 1.0, 0.0 ); glVertex3d( 0.0, 1.0, 1.0 );
    glVertex3d( 1.0, 1.0, 1.0 ); glVertex3d( 1.0, 1.0, 0.0 ); 
    glEnd();
}

void ParticleSystem::drawWalls() {
	cm.drawWalls();
}

void ParticleSystem::setEmitterPosition(float x, float y, float z, float yRotation, float zRotation) {
    emitterPos[0] = x;
    emitterPos[1] = y;
    emitterPos[2] = z;

	//The Initial velocity angle has to be transform according to the rotations.
}

void ParticleSystem::setEmitterNormal(float x, float y, float z) {
    emitterNormal[0] = x;
    emitterNormal[1] = y;
    emitterNormal[2] = z;
}

/** Adds the current configuration of particles to
  * your data structure for storing baked particles **/
void ParticleSystem::bakeParticles(float t) 
{

	// TODO (baking is extra credit)
}

/** Clears out your data structure of baked particles */
void ParticleSystem::clearBaked()
{

	// TODO (baking is extra credit)
}
